﻿using System;

namespace MathService
{
    /// <summary>Интервальный предикат</summary>
    [Serializable]
    //[System.ComponentModel.TypeConverter(typeof(IntervalConverter))]
    public class TimeInterval
    {
        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Включена ли нижняя граница интервала?</summary>
        private bool f_MinInclude = true;
        /// <summary>Включена ли верхняя граница интервала?</summary>
        private bool f_MaxInclude = true;
        /// <summary>Нижняя граница интервала</summary>
        private TimeSpan f_Min;
        /// <summary>Верхняя граница интервала</summary>
        private TimeSpan f_Max;

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Включена ли нижняя граница интервала?</summary>
        public bool MinInclude { get { return f_MinInclude; } set { f_MinInclude = value; } }
        /// <summary>Включена ли верхняя граница интервала?</summary>
        public bool MaxInclude { get { return f_MaxInclude; } set { f_MaxInclude = value; } }

        /// <summary>Нижняя граница интервала</summary>
        public TimeSpan Min { get { return f_Min; } set { f_Min = value; } }
        /// <summary>Верхняя граница интервала</summary>
        public TimeSpan Max { get { return f_Max; } set { f_Max = value; } }
        /// <summary>Протяжённость интервала</summary>
        public TimeSpan Length
        {
            get { return f_Max - f_Min; }
            set
            {
                var lv_M = Middle;
                value = TimeSpan.FromSeconds(value.TotalSeconds / 2);
                f_Min = lv_M - value;
                f_Max = lv_M + value;
            }
        }

        public TimeSpan Middle
        {
            get { return TimeSpan.FromSeconds((f_Min + f_Max).TotalSeconds / 2); }
            set
            {
                var lv_L2 = TimeSpan.FromSeconds(Length.TotalSeconds / 2);
                f_Min = value - lv_L2;
                f_Max = value + lv_L2;
            }
        }

        /// <summary>Интервал</summary>
        /// <param name="Max">Верхняя граница интервала</param>
        public TimeInterval(TimeSpan Max)
        {
            f_Max = Max;
        }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="Max">Верзняя граница интервала</param>
        public TimeInterval(TimeSpan Min, TimeSpan Max)
            : this(Max)
        {
            f_Min = Min;
        }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="Max">Верхняя граница интервала</param>
        /// <param name="IncludeLimits">Включать пределы?</param>
        public TimeInterval(TimeSpan Min, TimeSpan Max, bool IncludeLimits)
            : this(Min, IncludeLimits, Max, IncludeLimits) { }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="MinInclude">Включена ли нижняя граница интервала?</param>
        /// <param name="Max">Верхняя граница интервала</param>
        public TimeInterval(TimeSpan Min, bool MinInclude, TimeSpan Max)
            : this(Min, Max)
        {
            f_MinInclude = MinInclude;
        }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="MinInclude">Включена ли нижняя граница интервала?</param>
        /// <param name="Max">Верхняя граница интервала</param>
        /// <param name="MaxInclude">Включена ли верхняя граница интервала</param>
        public TimeInterval(TimeSpan Min, bool MinInclude, TimeSpan Max, bool MaxInclude)
            : this(Min, MinInclude, Max)
        {
            f_MaxInclude = MaxInclude;
        }

        /// <summary>Проверка на входжение в интервал</summary>
        /// <param name="X">Проверяемая величина</param>
        /// <returns>Истина, если величина входит в интервал</returns>
        public bool Check(TimeSpan X)
        {
            return (f_MinInclude && X == f_Min) ||
                (f_MaxInclude && X == f_Max) || (X > f_Min && X < f_Max);
        }

        public bool Check(TimeSpan X, TimeSpan MinOffset, TimeSpan MaxOffset)
        {
            var lv_Min = f_Min + MinOffset;
            var lv_Max = f_Max + MaxOffset;

            return (f_MinInclude && X == lv_Min) ||
                (f_MaxInclude && X == lv_Max) || (X > lv_Min && X < lv_Max);
        }

        public bool Check(TimeSpan X, TimeSpan Offset)
        {
            return Check(X, Offset, -Offset);
        }

        public TimeSpan Nomalize(TimeSpan X)
        {
            if(X > f_Max) return f_Max;
            return X < f_Min ? f_Min : X;
        }

        public bool IsExclude(TimeInterval I)
        {
            return !IsInclude(I);
        }

        /// <summary>
        /// Проверка вхождения интервала в интервал
        /// </summary>
        /// <param name="I">Проверяемый интервал</param>
        /// <returns>Истина, если проверяемый интервал входит</returns>
        public bool IsInclude(TimeInterval I)
        {
            return
                Check(I.f_MinInclude ? I.f_Min : I.f_Min + TimeSpan.FromTicks(1))
                && Check(I.f_MaxInclude ? I.f_Max : I.f_Max - TimeSpan.FromTicks(1));
        }

        public bool IsIntersect(TimeInterval I)
        {
            if(f_Min == I.f_Min && f_Max == I.f_Max) return true;

            var lv_MinInclude = Check(I.f_Min);
            if(!I.f_MinInclude && I.f_Min == f_Max) lv_MinInclude = false;

            var lv_MaxInclude = Check(I.f_Max);
            if(!I.f_MaxInclude && I.f_Max == f_Min) lv_MaxInclude = false;

            return lv_MinInclude || lv_MaxInclude;
        }

        public override string ToString()
        {
            return string.Format("{0}{2}; {3}{1}",
                      MinInclude ? "[" : "(", MaxInclude ? "]" : ")", Min, Max);
        }

        /// <summary>Оператор неявного приведения типа к предикату</summary>
        /// <param name="I">Интервал</param>
        /// <returns>Предикат от вещественного типа двойной точности</returns>
        public static implicit operator Predicate<TimeSpan>(TimeInterval I) { return I.Check; }

        public static implicit operator TimeSpan(TimeInterval I) { return I.Length; }
        public static explicit operator TimeInterval(TimeSpan V) { return new TimeInterval(TimeSpan.Zero, true, V, true); }

        /// <summary>Оператор проверки на вхоождение величины в интервал</summary>
        /// <param name="x">Проверяемая величина</param>
        /// <param name="I">Интервал</param>
        /// <returns>Истина, если величина внутри интервала</returns>
        public static bool operator ^(TimeSpan x, TimeInterval I) { return I.Check(x); }
        /// <summary>Оператор проверки на вхоождение величины в интервал</summary>
        /// <param name="X">Проверяемая величина</param>
        /// <param name="I">Интервал</param>
        /// <returns>Истина, если величина внутри интервала</returns>
        public static bool operator ^(TimeInterval I, TimeSpan X) { return X ^ I; }
    }

    /// <summary>Интервальный предикат</summary>
    [Serializable]
    //[System.ComponentModel.TypeConverter(typeof(IntervalConverter))]
    public class DateTimeInterval
    {
        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Включена ли нижняя граница интервала?</summary>
        private bool f_MinInclude = true;
        /// <summary>Включена ли верхняя граница интервала?</summary>
        private bool f_MaxInclude = true;
        /// <summary>Нижняя граница интервала</summary>
        private DateTime f_Min;
        /// <summary>Верхняя граница интервала</summary>
        private DateTime f_Max;

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Включена ли нижняя граница интервала?</summary>
        public bool MinInclude { get { return f_MinInclude; } set { f_MinInclude = value; } }
        /// <summary>Включена ли верхняя граница интервала?</summary>
        public bool MaxInclude { get { return f_MaxInclude; } set { f_MaxInclude = value; } }

        /// <summary>Нижняя граница интервала</summary>
        public DateTime Min { get { return f_Min; } set { f_Min = value; } }
        /// <summary>Верхняя граница интервала</summary>
        public DateTime Max { get { return f_Max; } set { f_Max = value; } }
        /// <summary>Протяжённость интервала</summary>
        public DateTime Length
        {
            get { return new DateTime(f_Max.Ticks - f_Min.Ticks); }
            set
            {
                var lv_M = Middle;
                var tics = value.Ticks / 2;
                f_Min = new DateTime(lv_M.Ticks - tics);
                f_Max = new DateTime(lv_M.Ticks + tics);
            }
        }

        public DateTime Middle
        {
            get { return new DateTime((f_Min.Ticks + f_Max.Ticks) / 2); }
            set
            {
                var lv_L2 = Length.Ticks / 2;
                f_Min = new DateTime(value.Ticks - lv_L2);
                f_Max = new DateTime(value.Ticks + lv_L2);
            }
        }

        /// <summary>Интервал</summary>
        /// <param name="Max">Верхняя граница интервала</param>
        public DateTimeInterval(DateTime Max)
        {
            f_Max = Max;
        }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="Max">Верзняя граница интервала</param>
        public DateTimeInterval(DateTime Min, DateTime Max)
            : this(Max)
        {
            f_Min = Min;
        }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="Max">Верхняя граница интервала</param>
        /// <param name="IncludeLimits">Включать пределы?</param>
        public DateTimeInterval(DateTime Min, DateTime Max, bool IncludeLimits)
            : this(Min, IncludeLimits, Max, IncludeLimits) { }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="MinInclude">Включена ли нижняя граница интервала?</param>
        /// <param name="Max">Верхняя граница интервала</param>
        public DateTimeInterval(DateTime Min, bool MinInclude, DateTime Max)
            : this(Min, Max)
        {
            f_MinInclude = MinInclude;
        }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="MinInclude">Включена ли нижняя граница интервала?</param>
        /// <param name="Max">Верхняя граница интервала</param>
        /// <param name="MaxInclude">Включена ли верхняя граница интервала</param>
        public DateTimeInterval(DateTime Min, bool MinInclude, DateTime Max, bool MaxInclude)
            : this(Min, MinInclude, Max)
        {
            f_MaxInclude = MaxInclude;
        }

        /// <summary>Проверка на входжение в интервал</summary>
        /// <param name="X">Проверяемая величина</param>
        /// <returns></returns>
        public bool Check(DateTime X)
        {
            return (f_MinInclude && X == f_Min) || (f_MaxInclude && X == f_Max) || (X > f_Min && X < f_Max);
        }

        public bool Check(DateTime X, TimeSpan MinOffset, TimeSpan MaxOffset)
        {
            var lv_Min = f_Min + MinOffset;
            var lv_Max = f_Max + MaxOffset;

            return (f_MinInclude && X == lv_Min) ||
                (f_MaxInclude && X == lv_Max) || (X > lv_Min && X < lv_Max);
        }

        public bool Check(DateTime X, TimeSpan Offset)
        {
            return Check(X, Offset, -Offset);
        }

        public DateTime Nomalize(DateTime X)
        {
            if(X > f_Max) return f_Max;
            return X < f_Min ? f_Min : X;
        }

        public bool IsExclude(DateTimeInterval I)
        {
            return !IsInclude(I);
        }

        public bool IsInclude(DateTimeInterval I)
        {
            return
                Check(I.f_MinInclude ? I.f_Min : I.f_Min + TimeSpan.FromTicks(1))
                && Check(I.f_MaxInclude ? I.f_Max : I.f_Max - TimeSpan.FromTicks(1));
        }

        public bool IsIntersect(DateTimeInterval I)
        {
            if(I.Min == Min && I.Max == Max) return true;

            var lv_MinInclude = Check(I.Min);
            if(!I.MinInclude && I.Min == Max) lv_MinInclude = false;

            var lv_MaxInclude = Check(I.Max);
            if(!I.MaxInclude && I.Max == Min) lv_MaxInclude = false;

            return lv_MinInclude || lv_MaxInclude;
        }

        public override string ToString()
        {
            return string.Format("{0}{2};{3}{1}",
                      MinInclude ? "[" : "(", MaxInclude ? "]" : ")", Min, Max);
        }

        /// <summary>Оператор неявного приведения типа к предикату</summary>
        /// <param name="I">Интервал</param>
        /// <returns>Предикат от вещественного типа двойной точности</returns>
        public static implicit operator Predicate<DateTime>(DateTimeInterval I) { return I.Check; }

        public static implicit operator DateTime(DateTimeInterval I) { return I.Length; }
        public static explicit operator DateTimeInterval(DateTime V) { return new DateTimeInterval(DateTime.MinValue, true, V, true); }

        /// <summary>Оператор проверки на вхоождение величины в интервал</summary>
        /// <param name="x">Проверяемая величина</param>
        /// <param name="I">Интервал</param>
        /// <returns>Истина, если величина внутри интервала</returns>
        public static bool operator ^(DateTime x, DateTimeInterval I) { return I.Check(x); }
        /// <summary>Оператор проверки на вхоождение величины в интервал</summary>
        /// <param name="X">Проверяемая величина</param>
        /// <param name="I">Интервал</param>
        /// <returns>Истина, если величина внутри интервала</returns>
        public static bool operator ^(DateTimeInterval I, DateTime X) { return X ^ I; }
    }

}
